﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BEPUphysics;
using BEPUphysics.DataStructures;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using BEPUphysics.Entities;
using PloobsEngine.Modelo;
using PloobsEngine.SceneControl;

namespace PloobsEngine.Physics.Bepu
{
    public class TriangleMeshObject : IPhysicObject
    {        
        StaticTriangleGroup triangleGroup;
        IObject owner;
        TriangleMesh triangleMesh;
        Vector3 position;
        BoundingBox bb;

        public TriangleMesh TriangleMesh
        {
            get { return triangleMesh; }
            set { triangleMesh = value; }
        }

        public StaticTriangleGroup TriangleGroup
        {
            get { return triangleGroup; }
            set { triangleGroup = value; }
        }

        public TriangleMeshObject(IModelo model, Vector3 pos, Matrix rotation, Vector3 scale)
        {            
            this.rotation = rotation;
            this.Scale = scale;
            position = pos;
            StaticTriangleGroup.StaticTriangleGroupVertex[] vertices = null;
            int[] indices = null;
            ExtractData(ref vertices, ref indices, model);

            Vector3 min = vertices[0].Position;
            Vector3 max = min;
            for (int i = 1; i < vertices.Count();i++)
            {
                Vector3 p = vertices[i].Position;
                min = Vector3.Min(min, p);
                max = Vector3.Max(max, p);
            }

            triangleMesh = new TriangleMesh(vertices, indices, 0.05f);
            triangleGroup = new StaticTriangleGroup(triangleMesh);
            triangleGroup.WorldMatrix = Matrix.CreateScale(Scale) * Matrix.CreateTranslation(pos) * Rotation ;
            min = Vector3.Transform(min, triangleGroup.WorldMatrix);
            max = Vector3.Transform(max, triangleGroup.WorldMatrix);
            bb = new BoundingBox(min, max);

            
        }

        public TriangleMeshObject(Model model, Vector3 pos, Matrix rotation, Vector3 scale)
        {            
            this.rotation = rotation;
            this.Scale = scale;
            position = pos;
            StaticTriangleGroup.StaticTriangleGroupVertex[] vertices = null;            
            int[] indices = null;
            StaticTriangleGroup.GetVerticesAndIndicesFromModel(model, out vertices, out indices);
            triangleMesh = new TriangleMesh(vertices, indices, 0.05f);
            triangleGroup = new StaticTriangleGroup(triangleMesh);
            bb = ModelBuilderHelper.GetModelBB(model, true);            
            triangleGroup.WorldMatrix = Matrix.CreateScale(Scale) * Matrix.CreateTranslation(pos) * Rotation;
            bb.Min = Vector3.Transform(bb.Min, triangleGroup.WorldMatrix);
            bb.Max = Vector3.Transform(bb.Max, triangleGroup.WorldMatrix);

        }


        public TriangleMeshObject(Vector3 pos,IModelo model) 
        {            
            this.Scale = Vector3.One;
            position = pos;
            StaticTriangleGroup.StaticTriangleGroupVertex[] vertices = null;
            int[] indices = null;
            ExtractData(ref vertices, ref indices, model);

            Vector3 min = vertices[0].Position;
            Vector3 max = min;
            for (int i = 1; i < vertices.Count(); i++)
            {
                Vector3 p = vertices[i].Position;
                min = Vector3.Min(min, p);
                max = Vector3.Max(max, p);
            }

            triangleMesh = new TriangleMesh(vertices, indices, 0.05f);
            triangleGroup = new StaticTriangleGroup(triangleMesh);
            triangleGroup.WorldMatrix = Matrix.CreateScale(Scale) * Matrix.CreateTranslation(pos) * Rotation;
            min = Vector3.Transform(min, triangleGroup.WorldMatrix);
            max = Vector3.Transform(max, triangleGroup.WorldMatrix);
            bb = new BoundingBox(min, max);            
        }

        /// <summary>
        /// Helper to get the vertex and index List from the model. --- COOOOOOOOOOOOOOOOOOOOOOOOOOOOOOL --little(lot) magic HERE !!!!
        /// </summary>
        /// <param name="vertices"></param>
        /// <param name="indices"></param>
        /// <param name="model"></param>
        public void ExtractData(ref StaticTriangleGroup.StaticTriangleGroupVertex[] vert, ref int[] ind, IModelo model)
        {
            List<Vector3> vertices = new List<Vector3>();
            List<int> indices = new List<int>();           
            
            Matrix[] bones_ = model.getBonesTransformation();
            for (int i = 0; i < model.MeshNumber; i++)
            {
                Matrix xform;                
                xform = bones_[model.GetParentBoneIndex(i)];
                
                BatchInformation[] bi = model.GetBatchInformation(i);
                for (int j = 0; j < bi.Length; j++)
                {
                    BatchInformation info = bi[j];
                    int offset = vertices.Count;
                    Vector3[] a = new Vector3[info.NumVertices];
                    model.GetVertexBuffer(i).GetData<Vector3>(info.StreamOffset + info.BaseVertex * info.VertexStride,
                        a, 0, info.NumVertices, info.VertexStride);
                    //xform = xform * Matrix.CreateScale(this.scale);
                    for (int k = 0; k != a.Length; ++k)
                    {
                        Vector3.Transform(ref a[k], ref xform, out a[k]);
                    }
                    vertices.AddRange(a);

                    if (model.GetIndexBuffer(i).IndexElementSize != IndexElementSize.SixteenBits)
                    {
                        int[] s = new int[info.PrimitiveCount * 3];
                        model.GetIndexBuffer(i).GetData<int>(info.StartIndex * 2, s, 0, info.PrimitiveCount * 3);
                        for (int k = 0; k != info.PrimitiveCount; ++k)
                        {
                            indices.Add(s[k * 3 + 2] + offset);
                            indices.Add(s[k * 3 + 1] + offset);
                            indices.Add(s[k * 3 + 0] + offset);
                        }
                    }
                    else
                    {
                        short[] s = new short[info.PrimitiveCount * 3];
                        model.GetIndexBuffer(i).GetData<short>(info.StartIndex * 2, s, 0, info.PrimitiveCount * 3);
                        for (int k = 0; k != info.PrimitiveCount; ++k)
                        {
                            indices.Add(s[k * 3 + 2] + offset);
                            indices.Add(s[k * 3 + 1] + offset);
                            indices.Add(s[k * 3 + 0] + offset);
                        }
                    }                        
                }
            }    

            ind = indices.ToArray<int>();
            //ai credo !!!
            vert  = vertices.ConvertAll<StaticTriangleGroup.StaticTriangleGroupVertex>( (a) => new StaticTriangleGroup.StaticTriangleGroupVertex(a)).ToArray();        

        }

        public PhysicObjectTypes PhysicObjectTypes
        {
            get { return PhysicObjectTypes.TRIANGLEMESHOBJECT; }
        }

        #region IPhysicObject Members

        public Vector3 Position
        {
            get
            {
                return triangleGroup.WorldMatrix.Translation;
            }
            set
            {
                this.position = value;
                triangleGroup.WorldMatrix = Matrix.CreateScale(Scale) * Matrix.CreateTranslation(position) * Rotation;                    
            }
        }

        public Vector3 Scale
        {
            get;
            set;
        }
        private Matrix rotation = Matrix.Identity;
        public Matrix Rotation
        {
            get
            {
                return rotation;
            }
            set
            {
                rotation = value;
                triangleGroup.WorldMatrix = Matrix.CreateTranslation(position) * Rotation;                        
            }
        }

        public Vector3 FaceVector
        {
            get { return Vector3.Forward; }
        }

        public Matrix WorldMatrix
        {
            get { return triangleGroup.WorldMatrix; }
        }

        public Vector3 Velocity
        {
            get
            {
                return Vector3.Zero;
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public void Serialize(System.Xml.XmlTextWriter textWriter)
        {
            throw new NotImplementedException();
        }

        public void DeSerialize(System.Xml.XmlNode node, EngineStuff engine, IModelo model, Vector3 position, Matrix rotation)
        {
            throw new NotImplementedException();
        }

        public bool isMotionLess
        {
            get
            {
                return true;
            }
            set
            {
                System.Diagnostics.Debug.WriteLine("isMotionLess in triangle mesh should nt be called");
            }
        }

        public IObject ObjectOwner
        {
            get
            {
                return owner;
            }
            set
            {
                this.owner = value;
            }
        }

        public void Enable()
        {   
        }

        public void Disable()
        {            
        }

        public void ApplyImpulse(Vector3 force)
        {
            throw new NotImplementedException();
        }

        public BoundingBox BoundingBox
        {
            get { return bb; }
        }

        #endregion

        #region IPhysicObject Members


        public void ApplyTorque(Vector3 force)
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}
